#include <cstring>
#include <limits>
#include <exception>

extern "C" {
#include <arpa/inet.h>
}

#include "SimpleTag.h"

using namespace std;

// Private helpers
namespace {
  void putUint8(uint8 val, byte **data) {
    **data = val;
    *data += sizeof(uint8);
  }

  uint8 getUint8(byte **data) {
    uint8 val = **data;
    *data += sizeof(uint8);
    return val;
  }

  void putUint16(uint16 val, byte **data) {
    uint16 netVal = htons(val);
    memcpy(*data, reinterpret_cast<byte *>(&netVal), sizeof(uint16));
    *data += sizeof(uint16);
  }

  uint16 getUint16(byte **data) {
    uint16 netVal = 0;
    memcpy(reinterpret_cast<byte *>(&netVal), *data, sizeof(uint16));
    *data += sizeof(uint16);
    return ntohs(netVal);
  }

  void putUint32(uint32 val, byte **data) {
    uint32 netVal = htonl(val);
    memcpy(*data, reinterpret_cast<byte *>(&netVal), sizeof(uint32));
    *data += sizeof(uint32);
  }

  uint32 getUint32(byte **data) {
    uint32 netVal = 0;
    memcpy(reinterpret_cast<byte *>(&netVal), *data, sizeof(uint32));
    *data += sizeof(uint32);
    return ntohl(netVal);
  }
}

SimpleTag::SimpleTag(const std::string &stringValue)
  : type_(STRING_TYPE),
    stringValue_(stringValue) {

  if (stringValue_.size() > std::numeric_limits<uint16>::max()) {
    throw std::exception();
  }
}

SimpleTag::SimpleTag(uint32 uint32Value)
  : type_(UINT32_TYPE),
    uint32Value_(uint32Value) {
}

void
SimpleTag::encode(byte **data) const {
  encodeType(data);
  switch (type_) {
    case STRING_TYPE:
      encodeStringType(data);
      break;
    case UINT32_TYPE:
      encodeUint32Type(data);
      break;
  }
}

size_t
SimpleTag::encodedTagListSize(const SimpleTagList &tagList) {
  size_t size = sizeof(uint16); // 2 bytes for tag count
  SimpleTagList::const_iterator i = tagList.begin();
  for (; i != tagList.end(); i++) {
    const SimpleTag &tag = *i;
    size += sizeof(byte); // 1 byte for tag type
    switch (tag.type_) {
      case STRING_TYPE:
        size += sizeof(uint16); // 2 bytes for size of string
        size += tag.stringValue_.size();
        break;
      case UINT32_TYPE:
        size += sizeof(uint32);
        break;
    }
  }
  return size;
}

void
SimpleTag::encodeTagList(const SimpleTagList &tagList, byte **data) {
  putUint16(static_cast<uint16>(tagList.size()), data);
  SimpleTagList::const_iterator i = tagList.begin();
  for(; i != tagList.end(); i++) {
    const SimpleTag &tag = *i;
    tag.encode(data);
  }
}

SimpleTag
SimpleTag::decode(byte **data) {
  SimpleTag::TagType type = decodeType(data);
  switch (type) {
    case STRING_TYPE:
      return SimpleTag(decodeStringType(data));
      break;
    case UINT32_TYPE:
      return SimpleTag(decodeUint32Type(data));
      break;
    default:
      throw std::exception();
  }
}

SimpleTagList
SimpleTag::decodeTagList(byte **data) {
  SimpleTagList tagList;
  uint16 tagCount = getUint16(data);
  for (int i = 0; i < tagCount; i++) {
    tagList.push_back(decode(data));
  }
  return tagList;
}

SimpleTag::TagType
SimpleTag::getType() const {
  return type_;
}

std::string
SimpleTag::getStringValue() const {
  if (type_ != STRING_TYPE) {
    throw std::exception();
  }
  return stringValue_;
}

uint32
SimpleTag::getUint32Value() const {
  if (type_ != UINT32_TYPE) {
    throw std::exception();
  }
  return uint32Value_;
}

void
SimpleTag::encodeType(byte **data) const {
  putUint8(static_cast<uint8>(type_), data);
}

void
SimpleTag::encodeStringType(byte **data) const {
  putUint16(static_cast<uint16>(stringValue_.size()), data);

  // skip the \0, we'll add it when we bring things back
  memcpy(*data, stringValue_.c_str(), stringValue_.size());
  *data += stringValue_.size();
}

void
SimpleTag::encodeUint32Type(byte **data) const {
  putUint32(uint32Value_, data);
}

// if this were for real, all of these should be written "securely"

SimpleTag::TagType
SimpleTag::decodeType(byte **data) {
  return static_cast<SimpleTag::TagType>(getUint8(data));
}

std::string
SimpleTag::decodeStringType(byte **data) {
  size_t size = getUint16(data);
  char *str = new char[size + 1];
  memcpy(str, *data, size);
  str[size] = '\0';
  std::string newStr(str);
  delete[] str;
  *data += size;
  return newStr;
}

uint32
SimpleTag::decodeUint32Type(byte **data) {
  return getUint32(data);
}

